﻿
//	------------------------------------------------------------------------------
//                                       _                          
//              _ __ ___   ___ _ __ ___ | |__  _ __ __ _ _ __   ___ 
//             | '_ ` _ \ / _ \ '_ ` _ \| '_ \| '__/ _` | '_ \ / _ \
//             | | | | | |  __/ | | | | | |_) | | | (_| | | | |  __/
//             |_| |_| |_|\___|_| |_| |_|_.__/|_|  \__,_|_| |_|\___|
//                                                     
//	------------------------------------------------------------------------------

#region - License -
//
//	------------------------------------------------------------------------------                                                   
//	         Copyright (c) 2010 Peter Vyvey (peter.vyvey@chiliware.be)
//	------------------------------------------------------------------------------
//
//
//	This file is part of CHILIWARE MEMBRANE.
//
//	CHILIWARE MEMBRANE is free software: you can redistribute it and/or modify
//	it under the terms of the GNU Lesser General Public License as published by
//	the Free Software Foundation, either version 3 of the License, or
//	(at your option) any later version.
//
//	CHILIWARE MEMBRANE is distributed in the hope that it will be useful,
//	but WITHOUT ANY WARRANTY; without even the implied warranty of
//	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//	GNU Lesser General Public License for more details.
//
//	You should have received a copy of the GNU Lesser General Public License
//	along with CHILIWARE MEMBRANE.  If not, see <http://www.gnu.org/licenses/>.
//
//
#endregion

#region - Changes -
//
//	peter.vyvey@chiliware.be		2010-08-10		Created
//
#endregion

#region - Using -

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Chiliware.Membrane.ExceptionManagement;

#endregion

namespace Chiliware.Membrane.Model
{
	#region - SessionService -

	/// <summary>
    /// Creates and manages the application's sessions.
    /// </summary>
    internal sealed partial class SessionService 
		: IDisposable
    {
        #region - Constructors -

        /// <summary>
        /// Default constructor.
        /// </summary>
        public SessionService() 
            : base() 
        {
            this.InitializeObject();
        }

        #endregion

        #region - Destructor -

        /// <summary>
        /// Destructor.
        /// </summary>
        ~SessionService()
        {
            this.Dispose(false);
        }

        #endregion

        #region - Private fields -

		/// <summary>
		/// TRUE if the object is disposed, FALSE otherwise.
		/// </summary>
		private bool isDisposed = false;

        /// <summary>
        /// A dictionary with the registered sessions and secure repositories.
        /// </summary>
		private readonly ConcurrentDictionary<Guid, SessionService.Session> sessions = new ConcurrentDictionary<Guid, SessionService.Session>();

		/// <summary>
		/// Token used to cancel the scavenging task.
		/// </summary>
		private CancellationTokenSource cancellationToken = new CancellationTokenSource();

        #endregion

        #region - Public methods -

        /// <summary>
        /// Disposes the object.
        /// </summary>
        public void Dispose()
        {
            this.Dispose(true);
            GC.SuppressFinalize(this);
        }

        #endregion

        #region - Internal methods -

        /// <summary>
        /// Create a new session.
        /// </summary>
		/// <param name="userID"></param>
		internal Guid CreateSession(string userID)
		{
			Guid sessionID = Guid.NewGuid();
			this.sessions[sessionID] = new SessionService.Session(userID);

			return sessionID;
		}

        /// <summary>
        /// Tries to retrieve a session object.
        /// </summary>
        /// <param name="sessionID">The session ID.</param>
        /// <param name="session">The session.</param>
        /// <returns>A flag indicating if the session was retrieved in the session repository.</returns>
		internal bool TryGetSession(Guid sessionID, out SessionService.Session session)
		{
			bool result = this.sessions.TryGetValue(sessionID, out session);

			return result;
		}

        /// <summary>
        /// Destroys the session with the given session identifier.
        /// </summary>
        /// <param name="sessionID">The session identifier.</param>
		internal void DestroySession(Guid sessionID)
		{
			SessionService.Session session;
			if (this.sessions.TryGetValue(sessionID, out session))
			{
				this.sessions.TryRemove(sessionID, out session);
			}
			else
			{
				throw new SessionException(ExceptionNumber.InvalidSession);
			}
		}

        #endregion

        #region - Private & protected methods -

        /// <summary>
        /// Initializes the SessionProvider.
        /// </summary>
        private void InitializeObject()
        {
            this.StartScavengingTask();
        }

        /// <summary>
        /// Start the sessions SCAVENGING task on a seperate thread.
        /// </summary>
		private void StartScavengingTask()
		{
			Task task = Task.Factory.StartNew(obj =>
			{
				Thread.CurrentThread.Priority = ThreadPriority.Lowest;
				while (!this.cancellationToken.IsCancellationRequested)
				{
					// Find the sessions that have not been used during the TIMEOUT interval
					List<Guid> expiredSessionIDs = this.sessions
													.AsParallel()
													.Where(s => s.Value.LastActivity < DateTime.UtcNow.Subtract(SessionService.Session.TIMEOUT))
													.Select(item => item.Key)
													.ToList();
					
					// Destroy the expired sessions
					foreach (var sessionID in expiredSessionIDs)
					{
						this.DestroySession(sessionID);
					}

					Thread.Sleep(100);
				}
			}, this.cancellationToken);
		}

        /// <summary>
        /// Disposes managed and unmanaged resources.
        /// </summary>
        /// <param name="isDisposing">Flag indicating how this protected method was called. 
        /// TRUE means via Dispose(), FALSE means via the destructor.
        /// Only in case of a call through the Dispose() method should managed resources be freed.</param>
        private void Dispose(bool isDisposing)
        {
            if (isDisposing)
            {
                // Free managed resources here
            }

			if (!this.isDisposed) 
			{
				if (null != this.cancellationToken)
				{
					if (!this.cancellationToken.Token.IsCancellationRequested)
					{
						this.cancellationToken.Cancel();
					}
				}
			}

			this.isDisposed = true;
        }

        #endregion
    }

    #endregion
}
